﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;
using System.Collections.Specialized;
using System.Security;
using System.IO;

namespace Idlesoft.Utils 
{
	public class AsyncProcess : Process
	{
		#region StreamState
		public class StreamState
		{
			#region ctors
			////public StreamState(System.IO.Stream stream, OutputReceivedDlg callback)
			public StreamState(System.IO.Stream stream, Action<string> callback)

			{
				Buffer = new byte[DefaultBufferSize];
				Stream = stream;
				Callback = callback;
			}
			#endregion ctors

			#region pubs
			public System.IO.Stream Stream;
			public byte[] Buffer;
			//public OutputReceivedDlg Callback;
			public Action<string> Callback;
			public const int DefaultBufferSize = 2048;
			#endregion pubs
		}
		#endregion StreamState

		#region dlgs
		public Action<string> StdOut { get; set; }
		public Action<string> StdErr { get; set; }
		public StreamWriter StdIn { get { return base.StandardInput; } }
		#endregion dlgs
		
		#region ctors
		public AsyncProcess() : base() 
		{
			_AsyncCallback = new System.AsyncCallback(ReadCompleted);
		}
		public static AsyncProcess Start(AsyncProcessStartInfo inAsyncProcessStartInfo)
		{
			AsyncProcess ap = new AsyncProcess();
			ap.StartInfo = inAsyncProcessStartInfo;
			ap.Start();
			return ap;
		}
		#endregion ctors

		#region props
		public new AsyncProcessStartInfo StartInfo
		{
			get 
			{ 
				AsyncProcessStartInfo aspi = AsyncProcessStartInfo.Wrap(base.StartInfo);
				aspi.StdOut = StdOut;
				aspi.StdErr = StdErr;
				return aspi;
			}
			set 
			{ 
				base.StartInfo = AsyncProcessStartInfo.UnWrap(value);
				StdOut = value.StdOut;
				StdErr = value.StdErr;
			}
		}
		#endregion props
		
		#region methods
		public new bool Start()
		{
			if (StdOut == null || StdErr == null)
				return false;

			bool result = false;
			try
			{
				if (base.Start())
				{
					KickoffAsyncReading(this.StandardOutput.BaseStream, StdOut);
					KickoffAsyncReading(this.StandardError.BaseStream, StdErr);
					result = true;
				}
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex.Message);
			}
			return result;
		}
		private void ReadCompleted(System.IAsyncResult asyncResult)
		{
			StreamState state = (StreamState)asyncResult.AsyncState;
			int BytesRead = state.Stream.EndRead(asyncResult);
			if (BytesRead > 0)
			{
				if (state.Callback != null)
					state.Callback(System.Text.Encoding.ASCII.GetString(state.Buffer, 0, BytesRead));

				System.Threading.Thread.Sleep(20);
				// repeat: 
				state.Stream.BeginRead(state.Buffer, 0, state.Buffer.Length, _AsyncCallback, state);
			}
		}

		private void KickoffAsyncReading(System.IO.Stream stream, Action<string> callback)
		{
			// initialize the state for this async read loop: 
			StreamState state = new StreamState(stream, callback);
			System.Threading.Thread.Sleep(20);
			stream.BeginRead(
				state.Buffer,				// where to put the results
				0,                          // offset
				state.Buffer.Length,        // how many bytes (BUFFER_SIZE)
				_AsyncCallback,				// ReadCompleted call back delegate
				state);                     // local state object
		}
	
		#endregion methods

		#region fields
		private AsyncCallback _AsyncCallback;
		#endregion fields
	}
	// Summary:
	//     Specifies a set of values that are used when you start a process.
	[TypeConverter(typeof(ExpandableObjectConverter))]
	public sealed class AsyncProcessStartInfo
	{
		public Action<string> StdOut { get; set; }
		public Action<string> StdErr { get; set; }

		internal static ProcessStartInfo UnWrap(AsyncProcessStartInfo apsi) { return apsi._ProcessStartInfo; }
		internal static AsyncProcessStartInfo Wrap(ProcessStartInfo psi) { return new AsyncProcessStartInfo(psi); }
		internal AsyncProcessStartInfo(ProcessStartInfo inPsi)
		{
			_ProcessStartInfo = PreparePsi(inPsi);
		}
		// Summary:
		//     Initializes a new instance of the System.Diagnostics.ProcessStartInfo class
		//     without specifying a file name with which to start the process.
		public AsyncProcessStartInfo()
		{
			_ProcessStartInfo = PreparePsi(new ProcessStartInfo());
		}
		//
		// Summary:
		//     Initializes a new instance of the System.Diagnostics.ProcessStartInfo class
		//     and specifies a file name such as an application or document with which to
		//     start the process.
		//
		// Parameters:
		//   fileName:
		//     An application or document with which to start a process.
		public AsyncProcessStartInfo(string fileName)
		{
			_ProcessStartInfo = PreparePsi(new ProcessStartInfo(fileName));
		}
		//
		// Summary:
		//     Initializes a new instance of the System.Diagnostics.ProcessStartInfo class,
		//     specifies an application file name with which to start the process, and specifies
		//     a set of command-line arguments to pass to the application.
		//
		// Parameters:
		//   fileName:
		//     An application with which to start a process.
		//
		//   arguments:
		//     Command-line arguments to pass to the application when the process starts.
		public AsyncProcessStartInfo(string fileName, string arguments)
		{
			_ProcessStartInfo = PreparePsi(new ProcessStartInfo(fileName, arguments));
		}

		// Summary:
		//     Gets or sets the set of command-line arguments to use when starting the application.
		//
		// Returns:
		//     File type–specific arguments that the system can associate with the application
		//     specified in the System.Diagnostics.ProcessStartInfo.FileName property. The
		//     default is an empty string (""). The maximum string length is 2,003 characters
		//     in .NET Framework applications and 488 characters in .NET Compact Framework
		//     applications.
		[NotifyParentProperty(true)]
		[DefaultValue("")]
		[MonitoringDescription("ProcessArguments")]
		[SettingsBindable(true)]
		[TypeConverter("System.Diagnostics.Design.StringValueConverter, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
		public string Arguments
		{
			get { return _ProcessStartInfo.Arguments; }
			set { _ProcessStartInfo.Arguments = value; }
		}
		//
		// Summary:
		//     Gets or sets a value indicating whether to start the process in a new window.
		//
		// Returns:
		//     true to start the process without creating a new window to contain it; otherwise,
		//     false. The default is false.
		//[DefaultValue(false)]
		//[MonitoringDescription("ProcessCreateNoWindow")]
		//[NotifyParentProperty(true)]
		//public bool CreateNoWindow { get; set; }
		//
		// Summary:
		//     Gets or sets a value that identifies the domain to use when starting the
		//     process.
		//
		// Returns:
		//     The Active Directory domain to use when starting the process. The domain
		//     property is primarily of interest to users within enterprise environments
		//     that use Active Directory.
		[NotifyParentProperty(true)]
		public string Domain
		{
			get { return _ProcessStartInfo.Domain; }
			set { _ProcessStartInfo.Domain = value; }
		}
		//
		// Summary:
		//     Gets search paths for files, directories for temporary files, application-specific
		//     options, and other similar information.
		//
		// Returns:
		//     A System.Collections.Specialized.StringDictionary that provides environment
		//     variables that apply to this process and child processes. The default is
		//     null.
		[Editor("System.Diagnostics.Design.StringDictionaryEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
		[NotifyParentProperty(true)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
		[DefaultValue("")]
		[MonitoringDescription("ProcessEnvironmentVariables")]
		public StringDictionary EnvironmentVariables { get { return _ProcessStartInfo.EnvironmentVariables; } }
		//
		// Summary:
		//     Gets or sets a value indicating whether an error dialog box is displayed
		//     to the user if the process cannot be started.
		//
		// Returns:
		//     true to display an error dialog box on the screen if the process cannot be
		//     started; otherwise, false.
		[MonitoringDescription("ProcessErrorDialog")]
		[NotifyParentProperty(true)]
		[DefaultValue(false)]
		public bool ErrorDialog
		{
			get { return _ProcessStartInfo.ErrorDialog; }
			set { _ProcessStartInfo.ErrorDialog = value; }
		}
		//
		// Summary:
		//     Gets or sets the window handle to use when an error dialog box is shown for
		//     a process that cannot be started.
		//
		// Returns:
		//     An System.IntPtr that identifies the handle of the error dialog box that
		//     results from a process start failure.
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		[Browsable(false)]
		public IntPtr ErrorDialogParentHandle
		{
			get { return _ProcessStartInfo.ErrorDialogParentHandle; }
			set { _ProcessStartInfo.ErrorDialogParentHandle = value; }
		}
		//
		// Summary:
		//     Gets or sets the application or document to start.
		//
		// Returns:
		//     The name of the application to start, or the name of a document of a file
		//     type that is associated with an application and that has a default open action
		//     available to it. The default is an empty string ("").
		[NotifyParentProperty(true)]
		[TypeConverter("System.Diagnostics.Design.StringValueConverter, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
		[DefaultValue("")]
		[Editor("System.Diagnostics.Design.StartFileNameEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
		[MonitoringDescription("ProcessFileName")]
		[SettingsBindable(true)]
		public string FileName
		{
			get { return _ProcessStartInfo.FileName; }
			set { _ProcessStartInfo.FileName = value; }
		}
		//
		// Summary:
		//     Gets or sets a value that indicates whether the Windows user profile is to
		//     be loaded from the registry.
		//
		// Returns:
		//     true to load the Windows user profile; otherwise, false.
		[NotifyParentProperty(true)]
		public bool LoadUserProfile
		{
			get { return _ProcessStartInfo.LoadUserProfile; }
			set { _ProcessStartInfo.LoadUserProfile = value; }
		}
		//
		// Summary:
		//     Gets or sets a secure string that contains the user password to use when
		//     starting the process.
		//
		// Returns:
		//     A System.Security.SecureString that contains the user password to use when
		//     starting the process.
		public SecureString Password
		{
			get { return _ProcessStartInfo.Password; }
			set { _ProcessStartInfo.Password = value; }
		}
		//
		// Summary:
		//     Gets or sets a value that indicates whether the error output of an application
		//     is written to the System.Diagnostics.Process.StandardError stream.
		//
		// Returns:
		//     true to write error output to System.Diagnostics.Process.StandardError; otherwise,
		//     false.
		//[NotifyParentProperty(true)]
		//[MonitoringDescription("ProcessRedirectStandardError")]
		//[DefaultValue(false)]
		//public bool RedirectStandardError { get; set; }
		//
		// Summary:
		//     Gets or sets a value indicating whether the input for an application is read
		//     from the System.Diagnostics.Process.StandardInput stream.
		//
		// Returns:
		//     true to read input from System.Diagnostics.Process.StandardInput; otherwise,
		//     false.
		//[DefaultValue(false)]
		//[NotifyParentProperty(true)]
		//[MonitoringDescription("ProcessRedirectStandardInput")]
		//public bool RedirectStandardInput { get; set; }
		//
		// Summary:
		//     Gets or sets a value that indicates whether the output of an application
		//     is written to the System.Diagnostics.Process.StandardOutput stream.
		//
		// Returns:
		//     true to write output to System.Diagnostics.Process.StandardOutput; otherwise,
		//     false.
		//[NotifyParentProperty(true)]
		//[DefaultValue(false)]
		//[MonitoringDescription("ProcessRedirectStandardOutput")]
		//public bool RedirectStandardOutput { get; set; }
		//
		// Summary:
		//     Gets or sets the preferred encoding for error output.
		//
		// Returns:
		//     An System.Text.Encoding object that represents the preferred encoding for
		//     error output. The default is null.
		public Encoding StandardErrorEncoding
		{
			get { return _ProcessStartInfo.StandardErrorEncoding; }
			set { _ProcessStartInfo.StandardErrorEncoding = value; }
		}
		//
		// Summary:
		//     Gets or sets the preferred encoding for standard output.
		//
		// Returns:
		//     An System.Text.Encoding object that represents the preferred encoding for
		//     standard output. The default is null.
		public Encoding StandardOutputEncoding
		{
			get { return _ProcessStartInfo.StandardOutputEncoding; }
			set { _ProcessStartInfo.StandardOutputEncoding = value; }
		}
		//
		// Summary:
		//     Gets or sets the user name to be used when starting the process.
		//
		// Returns:
		//     The user name to use when starting the process.
		[NotifyParentProperty(true)]
		public string UserName
		{
			get { return _ProcessStartInfo.UserName; }
			set { _ProcessStartInfo.UserName = value; }
		}
		//
		// Summary:
		//     Gets or sets a value indicating whether to use the operating system shell
		//     to start the process.
		//
		// Returns:
		//     true to use the shell when starting the process; otherwise, the process is
		//     created directly from the executable file. The default is true.
		//[MonitoringDescription("ProcessUseShellExecute")]
		//[DefaultValue(true)]
		//[NotifyParentProperty(true)]
		//public bool UseShellExecute { get; set; }
		//
		// Summary:
		//     Gets or sets the verb to use when opening the application or document specified
		//     by the System.Diagnostics.ProcessStartInfo.FileName property.
		//
		// Returns:
		//     The action to take with the file that the process opens. The default is an
		//     empty string ("").
		[NotifyParentProperty(true)]
		[DefaultValue("")]
		[TypeConverter("System.Diagnostics.Design.VerbConverter, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
		[MonitoringDescription("ProcessVerb")]
		public string Verb
		{
			get { return _ProcessStartInfo.Verb; }
			set { _ProcessStartInfo.Verb = value; }
		}
		//
		// Summary:
		//     Gets the set of verbs associated with the type of file specified by the System.Diagnostics.ProcessStartInfo.FileName
		//     property.
		//
		// Returns:
		//     The actions that the system can apply to the file indicated by the System.Diagnostics.ProcessStartInfo.FileName
		//     property.
		[Browsable(false)]
		[DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public string[] Verbs { get { return _ProcessStartInfo.Verbs; } }
		//
		// Summary:
		//     Gets or sets the window state to use when the process is started.
		//
		// Returns:
		//     A System.Diagnostics.ProcessWindowStyle that indicates whether the process
		//     is started in a window that is maximized, minimized, normal (neither maximized
		//     nor minimized), or not visible. The default is normal.
		//
		// Exceptions:
		//   System.ComponentModel.InvalidEnumArgumentException:
		//     The window style is not one of the System.Diagnostics.ProcessWindowStyle
		//     enumeration members.
		[MonitoringDescription("ProcessWindowStyle")]
		[NotifyParentProperty(true)]
		public ProcessWindowStyle WindowStyle
		{
			get { return _ProcessStartInfo.WindowStyle; }
			set { _ProcessStartInfo.WindowStyle = value; }
		}
		//
		// Summary:
		//     Gets or sets the initial directory for the process to be started.
		//
		// Returns:
		//     The fully qualified name of the directory that contains the process to be
		//     started. The default is an empty string ("").
		[TypeConverter("System.Diagnostics.Design.StringValueConverter, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
		[NotifyParentProperty(true)]
		[DefaultValue("")]
		[MonitoringDescription("ProcessWorkingDirectory")]
		[Editor("System.Diagnostics.Design.WorkingDirectoryEditor, System.Design, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", "System.Drawing.Design.UITypeEditor, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a")]
		[SettingsBindable(true)]
		public string WorkingDirectory
		{
			get { return _ProcessStartInfo.WorkingDirectory; }
			set { _ProcessStartInfo.WorkingDirectory = value; }
		}

		internal static ProcessStartInfo PreparePsi(ProcessStartInfo inPsi)
		{
			inPsi.RedirectStandardError = true;
			inPsi.RedirectStandardInput = true;
			inPsi.RedirectStandardOutput = true;
			inPsi.UseShellExecute = false;
			inPsi.CreateNoWindow = true;
			return inPsi;
		}

		#region fields
		ProcessStartInfo _ProcessStartInfo;
		#endregion fields
	}
}
